package edu.drexel.cs.eVoter;

import java.util.Date;

import org.apache.log4j.Logger;
import org.jgroups.Address;
import org.jgroups.JChannel;
import org.jgroups.ReceiverAdapter;
import org.jgroups.View;
import org.jgroups.blocks.ReplicatedHashMap;
import org.jgroups.blocks.RpcDispatcher;
import org.jgroups.util.Util;

/**
* This is a replicated EVoter server. The server maintains state which consists of
* an election and their corresponding values. When it is started, it tries to reach other
* EVoter servers to get its initial state. If it does not receive any response after 5
* seconds, it assumes it is the first server and starts processing requests.<p>
* Any updates are multicast across the cluster.
*/

public class EVoterServer extends ReceiverAdapter implements Runnable{
    private static Logger logger = Logger.getLogger(EVoterServer.class);
    private JChannel channel = null;
    private static Election election = null;
    private String stateName = null;    
    private static final String serverProperties = null; // default properties
    private static final String channelName = "StateServerGroup";
    private boolean isRunning = true;
    
    // State object(s):
    private static ReplicatedHashMap<String, Address> stateServerNameToAddressMap = null;
            
    public EVoterServer(String stateName)
    {
    	this.stateName = stateName;
    }
    
    public static void setElection(Election theElection)
    {
    	election = theElection;
    }
    
    public void viewAccepted(View newView) {
        System.out.println("Accepted view (" + newView.size() + newView.getMembers() + ").");
    }

    /* Election-related methods: */

	public boolean vote(String voterId, String candidateName)
	{
		return election.vote(voterId, candidateName);
	}
	
	public long result(String candidateName)
	{
		return election.result(candidateName);
	}
	
	public String results(String stateName)
	{		
		return election.results(stateName);
	}
	
	public String results()
	{	
		return election.results();
	}
	
	/* Thread-related methods: */
	
	@Override
	public void run() {
        try {
        	channel = new JChannel(serverProperties);
        	channel.setName(stateName);
        	//channel.setDiscardOwnMessages(true);
            //channel.connect(channelName);
            @SuppressWarnings("unused")
			RpcDispatcher dispatcher = new RpcDispatcher(channel, this, this, this);
            channel.connect(channelName);
            channel.getState(null, 0);
            if(stateServerNameToAddressMap == null)
            {
            	stateServerNameToAddressMap = new ReplicatedHashMap<String, Address>(
					channel);
            }
            
            if(!stateServerNameToAddressMap.containsKey(stateName))
            {
            	stateServerNameToAddressMap.put(stateName, channel.getAddress());
            	
            	// Register the state's channel if it has not already been done.
            	election.registerStateChannel(stateName);
            }
            
            logger.info(stateName + " server started at " + new Date() + ".");
            logger.info(stateName + " joined channel '" + channelName + "' (" + channel.getView().size() + " members).");
            logger.info(stateName + " server ready to serve requests.");
            logger.info(stateName + " server's channel address is " + channel.getAddress() + ".");
            
            isRunning = true;
            
            while(isRunning) {
                Util.sleep(10000);
            }
        }
        catch(Exception e) {
            logger.error("EVoterServer.run(); State: " + stateName + "; " + e.getMessage(), e);
        }
        finally
        {
           	Util.close(channel);
        }
	}

	public void stopServer()
	{
		// Remove the address of this state's server from the map.
		stateServerNameToAddressMap.remove(stateName);
		
		// Set the flag to false to end thread.
		isRunning = false;
	}
	
	public boolean isRunning()
	{
		return isRunning;
	}
	
	/* Address-related method: */
	
	public Address getStateServerAddress(String voterId)
	{
		State state = election.getVoterState(voterId);
		
		return stateServerNameToAddressMap.get(state.getName());
	}
	
	public Address getReadyStateServerAddress()
	{
		//return stateServerNameToAddressMap.get(this.election.getStates().get(0).getName());
		return channel.getAddress();
	}
	
	/* Create new state server method: */
	
	public void createNewStateServer(String voterId)
	{
		String voterStateName = election.getVoterState(voterId).getName();
		EVoterServer server = new EVoterServer(voterStateName);
		new Thread(server).start();
	}
}